///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2011 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CBINARYREADER_H_
#define _BUOLA_IO_CBINARYREADER_H_

#include <buola/io/cbufferstream.h>
#include <buola/io/serialization.h>

namespace buola { namespace io {

///\addtogroup io
///@{

class CBinaryReader
{
public:
    CBinaryReader(io::PBufferStream pStream,EEndianness pEndianness=EEndianness::SYSTEM)
        :   mStream(std::move(pStream))
        ,   mEndianness(pEndianness)
    {}

    CBinaryReader(const CURI &pURI,EMode pMode=EMode::READ,EEndianness pEndianness=EEndianness::SYSTEM);

    CResult Read(uint8_t *pData,size_t pSize)
    {
        return mStream->Read(pData,pSize);
    }
    
    void ReadAll(uint8_t *pData,size_t pSize)
    {
        if(mStream->Read(pData,pSize).Count()<pSize)
            throw XData("can't read in CBinaryReader");
    }
    
    CResult Ignore(size_t pLen,uint8_t pByte)
    {
        return mStream->Ignore(pLen,pByte);
    }
    
    CResult Ignore(size_t pLen)
    {
        return mStream->Ignore(pLen);
    }
    
    template<typename tInt>
    void ReadInteger(tInt &pInt,EEndianness pByteOrder,std::size_t pLen)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pInt);

        if(pByteOrder==EEndianness::LITTLE)
        {
            if(pLen>sizeof(tInt))
            {
                uint8_t lTmp[pLen];
                ReadAll(lTmp,pLen);
                memcpy(lDst,lTmp,sizeof(tInt));
            }
            else
            {
                ReadAll(lDst,pLen);

                if(pLen<sizeof(tInt))
                {
                    /// \todo Do this properly for signed integers
                    uint8_t lFill=0;

                    for(int i=pLen;i<sizeof(tInt);i++)
                        lDst[i]=lFill;
                }
            }
        }
        else
        {
            if(pLen>sizeof(tInt))
            {
                uint8_t lTmp[pLen];
                ReadAll(lTmp,pLen);

                for(int i=0;i<sizeof(tInt);i++)
                    lDst[i]=lTmp[pLen-i-1];
            }
            else
            {
                uint8_t lTmp[sizeof(tInt)];
                ReadAll(lTmp,pLen);

                for(int i=0;i<pLen;i++)
                    lDst[i]=lTmp[pLen-i-1];

                if(pLen<sizeof(tInt))
                {
                    /// \todo Do this properly for signed integers
                    uint8_t lFill=0;

                    for(int i=pLen;i<sizeof(tInt);i++)
                        lDst[i]=lFill;
                }
            }
        }
    }
    
    template<typename tInt>
    void ReadInteger(tInt &pInt)
    {
        ReadInteger(pInt,mEndianness,sizeof(pInt));
    }
    
    template<typename tFloat>
    void ReadReal(tFloat &pFloat,EEndianness pByteOrder)
    {
        uint8_t *lDst=reinterpret_cast<uint8_t*>(&pFloat);

        if(pByteOrder==EEndianness::LITTLE)
        {
            ReadAll(lDst,sizeof(pFloat));
        }
        else
        {
            uint8_t lBuf[sizeof(pFloat)];
            ReadAll(lBuf,sizeof(pFloat));
            for(int i=0;i<sizeof(pFloat);i++)
                lDst[i]=lBuf[sizeof(pFloat)-i-1];
        }
    }

    template<typename tFloat>
    void ReadReal(tFloat &pFloat)
    {
        ReadReal(pFloat,mEndianness);
    }
    
    template<typename tInt,OEnableIf<std::is_integral<tInt>> vDummy=1>
    CBinaryReader &operator>>(tInt &pInt)
    {
        ReadInteger(pInt);
        return *this;
    }
    
    template<typename tFloat,OEnableIf<std::is_floating_point<tFloat>> vDummy=1>
    CBinaryReader &operator>>(tFloat &pFloat)
    {
        ReadReal(pFloat);
        return *this;
    }
    
    template<typename tValue>
    tValue Get()
    {
        tValue lValue;
        *this >> lValue;
        return lValue;
    }

    const PBufferStream &Stream()   {   return mStream; }

private:
    io::PBufferStream mStream;
    EEndianness mEndianness;
};

template<typename tType>
auto operator>>(CBinaryReader &pS,tType &pType) -> OFirst<CBinaryReader&,decltype(deserialize(pS,pType))>
{
    deserialize(pS,pType);
    return pS;
}

///@}

/*namespace io*/ } /*namespace buola*/ }

#endif
